/******************************************************************************
 * MultiMouse
 * A multitouch mouse driver for FTIR surfaces
 * Ramsin Khoshabeh
 * Human-Computer Interaction Laboratory
 * University of California, San Diego 
 *
 * Copyright (c) 2008, Ramsin Khoshabeh
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the <organization> nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY RAMSIN KHOSHABEH ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL RAMSIN KHOSHABEH BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 * Changelog:
 * 06-01-08 - Added Task switching (alt-tab) functionality
 * 05-01-08 - First stable build
 ******************************************************************************/
#pragma once
#define WIN32_LEAN_AND_MEAN 
#define _WIN32_WINNT  0x0500

#include <windows.h>

#include <cv.h>
#include <cxcore.h>
#include <highgui.h>
#include <map>

#include <tchar.h>

#pragma comment( lib, "user32" )

#include "TouchScreenDevice.h"
#include "TouchData.h"

using namespace touchlib;

#include <stdio.h>
#include <cvcam.h>

static bool keystate[256];
bool ok = true;
ITouchScreen *screen;
static float SCR_WIDTH = GetSystemMetrics(SM_CXSCREEN);
static float SCR_HEIGHT = GetSystemMetrics(SM_CYSCREEN);
static const int DIST_THRESH = 300;
//static const int DIST_FACTOR = 1500000; //factor to multiple area when comparing blob distance
static const int TASK_THRESH = 100; //minimum pixels before switching task

#define TRACKPAD_MODE 0 //change to 1 for trackpad and 0 for absolute cursor position
#define TASKSWITCHER 1 //change to 1 to allow 'alt-tabbing' and 0 if not

class MouseApp : public ITouchListener
{
public:
	MouseApp()
	{
	}

	~MouseApp()
	{
	}
/******************************************************************************
 * Not extentions of ITouchListener....
 ******************************************************************************/
	std::vector<TouchData> getFingerList()
	{	
		//really should copy it, but eh..
		return fingerList;
	}

	std::vector<DWORD> getFingerState()
	{	
		//really should copy it, but eh..
		return fingerState;
	}

	double calcEuclidDist( TouchData data1, TouchData data2 )
	{

		double pixX1,pixX2,pixY1,pixY2;
		pixX1 = data1.X*SCR_WIDTH;
		pixY1 = data1.Y*SCR_HEIGHT;
		pixX2 = data2.X*SCR_WIDTH;
		pixY2 = data2.Y*SCR_HEIGHT;
		double dist = double( sqrt( (pixX1-pixX2)*(pixX1-pixX2)
			                       +(pixY1-pixY2)*(pixY1-pixY2) ) );
		return dist;
	}

	void sendMouseEvent( TouchData data, DWORD flags, DWORD wheelMotion )
	{
	//	//if cursor moves: dx = (data.X*65536.0f); dy = (data.Y*65536.0f);	
#ifdef _DEBUG
		switch( flags )
		{
		case 0x8001: //move (absolute coordinates)
			printf("MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE\n");
			break;
		case 0x8003: //left down and move (absolute coordinates)
			printf("MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE\n");
			break;
		case 0x0002: //left down
			printf("MOUSEEVENTF_LEFTDOWN\n");
			break;
		case 0x0004: //left up
			printf("MOUSEEVENTF_LEFTUP\n");
			break;
		case 0x0008: //right down
			printf("MOUSEEVENTF_RIGHTDOWN\n");
			break;
		case 0x0010: //right up
			printf("MOUSEEVENTF_RIGHTUP\n");
			break;
		case 0x0800: //scroll
			if( int(wheelMotion)>0 )//down
				printf("MOUSEEVENTF_WHEEL (scrolling down) ");
			else //up
				printf("MOUSEEVENTF_WHEEL (scrolling up) ");
			printf("wheelMotion=%d\n",int(wheelMotion));
			break;
		default:
			printf("UNDEFINED CASE: **** %x ****",flags );
			break;
		}
#endif
		INPUT aInput;
		aInput.type = INPUT_MOUSE;
		aInput.mi.dwFlags = flags;
		aInput.mi.dwExtraInfo = 0;
		aInput.mi.mouseData = 2*wheelMotion;//3*wheelMotion*WHEEL_DELTA;
		aInput.mi.time = 0;

		if( (flags=0x8001)||(flags==0x8003) )//move or initial left-click
		{
#if TRACKPAD_MODE
			//Trackpad mode adds in the offsetX/Y
			aInput.mi.dx = ((data.X + offsetX) * 65536.0f);
			aInput.mi.dy = ((data.Y + offsetY) * 65536.0f);
#else
			aInput.mi.dx = (data.X*65536.0f);
			aInput.mi.dy = (data.Y*65536.0f);
#endif
		}
		else
		{
			aInput.mi.dx = 0;
			aInput.mi.dy = 0;
		}

		int aResult = SendInput( 1,&aInput,sizeof(INPUT) );
	}

	void SendKeyPress(WORD key)
	{
#if _DEBUG
		printf("0x%x Key Pressed\n", key);
#endif
		// Prepare input struct
		INPUT aInput;
		aInput.type = INPUT_KEYBOARD;
		aInput.ki.wVk = key;
		aInput.ki.wScan = 0;//0x45;
		aInput.ki.time = 0;
		aInput.ki.dwFlags = KEYEVENTF_EXTENDEDKEY;
		aInput.ki.dwExtraInfo = 0;

		// Simulate a key press
		int aResult = SendInput( 1,&aInput,sizeof(INPUT) );
	}

	void SendKeyRelease(WORD key)
	{
#ifdef _DEBUG
		printf("0x%x Key Released\n", key);
#endif
		// Prepare input struct
		INPUT aInput;
		aInput.type = INPUT_KEYBOARD;
		aInput.ki.wVk = key;
		aInput.ki.wScan = 0;//0x45;
		aInput.ki.time = 0;
		aInput.ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP;
		aInput.ki.dwExtraInfo = 0;

		// Simulate a key release
		int aResult = SendInput( 1,&aInput,sizeof(INPUT) );
	}
/*****************************************************************************/

	//! Notify that a finger has just been made active. 
	virtual void fingerDown( TouchData data )
	{
		if( fingerList.size() == 0 ) //click or cursor move
		{
#if TRACKPAD_MODE
            //Get current position of the mouse
			POINT pos;
			GetCursorPos(&pos);
			
 			//Calculate the offset value
			offsetX = (pos.x/SCR_WIDTH) - data.X;
			offsetY = (pos.y/SCR_HEIGHT) - data.Y;		
#endif

			fingerList.push_back( data );
			sendMouseEvent( data, MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE, 0 );
			fingerState.push_back( 0 ); //no state till finger removed or moved
		}
		else if( fingerList.size() == 1 ) //click
		{
			//Another finger has control of cursor, so check if it's within 
			//DIST_THRESH of cursor (fingerList[0]), add it to fingerList, and
			//send left or right button down if it's to the left or right of
			//the cursor respectively.
			double distance = calcEuclidDist( fingerList[0], data );
			if( distance<DIST_THRESH )
			//if( distance<abs(DIST_FACTOR*fingerList[0].area) )
			{
				fingerList.push_back( data );
				if( data.X<fingerList[0].X ) //left button down
				{
					sendMouseEvent( data, MOUSEEVENTF_LEFTDOWN, 0 );
					fingerState.push_back( MOUSEEVENTF_LEFTDOWN );
				}
				else //right button down
				{
					sendMouseEvent( data, MOUSEEVENTF_RIGHTDOWN, 0 );
					fingerState.push_back( MOUSEEVENTF_RIGHTDOWN );
				}
			}
		}
		else if( fingerList.size() == 2 ) //scroll or taskswitch
		{
			//check if it's ok and add it to the list
			double distance = calcEuclidDist( fingerList[0], data );
			if( distance<DIST_THRESH )
			//if( distance<abs(DIST_FACTOR*fingerList[0].area) )
			{
				//three fingers in list, so we need to cancel the click
				//we had and release the button
				SendKeyPress(VK_CANCEL);
				if( fingerList[1].X < fingerList[0].X )
					sendMouseEvent( fingerList[1], MOUSEEVENTF_LEFTUP, 0 );
				else
					sendMouseEvent( fingerList[1], MOUSEEVENTF_RIGHTUP, 0 );

				//save current x-position for taskswitching
				currentPos = data.X;

				fingerList.push_back( data );
				fingerState.push_back( 0 );
			}
		}
	}

	//! Notify that a finger has moved.
	virtual void fingerUpdate( TouchData data )
	{
		if( fingerList.size() == 1 ) //cursor
		{
			if( data.ID == fingerList[0].ID )//make sure it's the right finger
			{
				fingerList[0] = data; //update the data
				fingerState[0] = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE;
				sendMouseEvent( data, MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE, 0 );
			}
		}
		else if( fingerList.size() == 2 ) //cursor with a click depressed
		{
			if( data.ID == fingerList[0].ID ) //move cursor
			{		
				fingerList[0] = data; //update the data
				fingerState[0] = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE;
				sendMouseEvent( data, MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE, 0 );
			}
			//click finger is moving, do nothing but update its state
			else if( data.ID == fingerList[1].ID )
			{
				fingerList[1] = data; //update the data
				fingerState[1] = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE;
			}
		}
		else if( fingerList.size() == 3 ) //scrolling or taskswitching
		{
			//doesn't matter what finger... 3 fingers down and moving
			//means that it is a scroll or taskswtich so find the current finger...
			int index;
			bool found = false;
			for( index=0; index<fingerList.size(); index++ )
			{
				if( data.ID == fingerList[index].ID )
				{
					found = true;
					break;
				}
			}
			
			if( !found ) //this should never be true
				return;

			////see if it's a valid input and scroll/taskswitch
			//double distance = calcEuclidDist( fingerList[0],data );
			//if( distance<DIST_THRESH ) //valid input
			//{
				//direction of motion
				float dx = data.X - fingerList[index].X;
				float dy = data.Y - fingerList[index].Y;
				if( abs(dy) > abs(dx) )//predominantly vertical motion, valid scroll
				{
					if( dy < 0 ) //up
					{
						fingerList[index] = data;
						fingerState[index] = MOUSEEVENTF_WHEEL;
						sendMouseEvent( data, MOUSEEVENTF_WHEEL, 0.4*SCR_HEIGHT*dy );
					}
					else //down
					{
						fingerList[index] = data;
						fingerState[index] = MOUSEEVENTF_WHEEL;
						sendMouseEvent( data, MOUSEEVENTF_WHEEL, 0.4*SCR_HEIGHT*dy );
					}
				}
				//switching tasks but only looking at first finger
				else if( data.ID == fingerList[0].ID )
				{
#if TASKSWITCHER
					//if the fingers move more than 'TASK_THRESH' in either x-direction
					if( abs((data.X-currentPos)*SCR_WIDTH) > TASK_THRESH )
					{
						//we don't want to press 'alt' more than once
						//we will release the 'alt' key when we finish
						if( !taskSwitch )
						{
							taskSwitch = true;
							SendKeyPress(VK_MENU); //alt pressed
						}

						//shift-alt-tab if moving left
						if( data.X-currentPos < 0 ) 
						{
							//send shift+tab
							SendKeyPress(VK_SHIFT);
							SendKeyPress(VK_TAB);
							SendKeyRelease(VK_SHIFT);
							SendKeyRelease(VK_TAB);
						}
						//alt-tab if moving right
						else if( data.X-currentPos > 0 )
						{
							//send tab
							SendKeyPress(VK_TAB);
							SendKeyRelease(VK_TAB);
						}
					
						//update the current position
						currentPos = data.X; 
					}
#endif
				}
			//}
		}
	}

	//! A finger is no longer active.
	virtual void fingerUp(TouchData data)
	{
		std::vector<TouchData>::iterator dIter;
		dIter = fingerList.begin();
		std::vector<DWORD>::iterator sIter;
		sIter = fingerState.begin();
		
		bool scrolling = false;
		for( int i=0; i<fingerState.size(); i++ )
		{
			if( fingerState[i]==0x0800 )
				scrolling = true;
		}

		//send the corresponding finger-up command unless it was scrolling or
		//alt-tabbing, in which case we already released the button
		for( int i=0; i<fingerList.size(); i++, dIter++, sIter++ )
		{
			if( data.ID == fingerList[i].ID )
			{
				if( !scrolling && !taskSwitch )
				{	//send left down then up if cursor finger hasn't moved
					if( (i == 0) && (fingerState[i]==0) )
					{
						sendMouseEvent( data, MOUSEEVENTF_LEFTDOWN | 
							MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE, 0 );
						sendMouseEvent( data, MOUSEEVENTF_LEFTUP, 0 );
					}
					else if( i == 1 ) //send a button up for second finger
					{
						if( data.X<fingerList[0].X ) //left button up
							sendMouseEvent( data, MOUSEEVENTF_LEFTUP, 0 );
						else //right button up
							sendMouseEvent( data, MOUSEEVENTF_RIGHTUP, 0 );
					}
				}

				if( taskSwitch ) //release 'alt' key
				{
					taskSwitch = false;
					SendKeyRelease( VK_MENU );
				}

				fingerList.erase(dIter);
				fingerState.erase(sIter);
				break;
			}
		}
	}

private:
	std::vector<TouchData> fingerList;	//data about each finger down
	std::vector<DWORD> fingerState;		//corresponding state of each finger
	bool taskSwitch;
	float currentPos;

#if TRACKPAD_MODE
	float offsetX;
	float offsetY;
#endif
};


MouseApp app;

int _tmain(int argc, char * argv[])
{
	screen = TouchScreenDevice::getTouchScreen();
	cvNamedWindow( "mywindow", CV_WINDOW_AUTOSIZE );

	screen->setDebugMode(false);
	std::string recLabel,bgLabel;
	if(!screen->loadConfig("config.xml"))
	{
		std::string capLabel = screen->pushFilter("dsvlcapture");
		screen->pushFilter("mono");
		screen->pushFilter("smooth");
		bgLabel = screen->pushFilter("backgroundremove");

		std::string bcLabel = screen->pushFilter("brightnesscontrast");
		recLabel = screen->pushFilter("rectify");

		screen->setParameter(recLabel, "level", "25");

		screen->setParameter(capLabel, "source", "cam");
		//screen->setParameter("capture1", "source", "../tests/simple-2point.avi");
		//screen->setParameter("capture1", "source", "../tests/hard-5point.avi");

		screen->setParameter(bcLabel, "brightness", "0.1");
		screen->setParameter(bcLabel, "contrast", "0.4");

		screen->saveConfig("config.xml");
	}
	else
	{
		recLabel = screen->findFirstFilter("rectify");				
		bgLabel = screen->findFirstFilter("backgroundremove");		
	}

	screen->registerListener((ITouchListener *)&app);
	// Note: Begin processing should only be called after the screen is set up

	screen->beginProcessing();
	
	SLEEP(1000);
	screen->setParameter(bgLabel, "mask", (char*)screen->getCameraPoints());
	screen->setParameter(bgLabel, "capture", "");
	screen->beginTracking();

	printf("***************************************************************************\n");
	printf("\n");
	printf("Usage:\n");
	printf("One Finger:\n");
	printf("   Left-click: tap finger.\n");
	printf("   Move cursor: hold and move the finger.\n");
	printf("Two Fingers:\n");
	printf("   Left-click: place first finger as cursor and tap second\n");
	printf("               finger to the left of it.\n");
	printf("   Right-click: place first finger as cursor and tap second\n");
	printf("                finger to the right of it.\n");
	printf("   Drag-and-drop: place first finger as cursor, press and hold\n");
	printf("                  second finger to the left of it, move around,\n");
	printf("                  and release second finger to drop.\n");
	printf("Three Fingers:\n");
	printf("   Scrolling: move up to scroll up and down to scroll down.\n");
	printf("   Alt-tabbing: move left to 'alt-tab' left, and right to 'alt-tab' right.\n");
	printf("\n");
	printf("***************************************************************************\n");
	do
	{
		//printf("Doing keypress\n");
		int keypressed = cvWaitKey(16) & 255;

		if(keypressed != 255 && keypressed > 0)
			printf("KP: %d\n", keypressed);
        if( keypressed == 27) break;		// ESC = quit
        if( keypressed == 98)				// b = recapture background
		{
			screen->setParameter(bgLabel, "capture", "");
			//app.clearFingers();
		}
        if( keypressed == 114)				// r = auto rectify..
		{
			screen->setParameter(recLabel, "level", "auto");
		}

  		screen->getEvents();
//std::vector<TouchData> fingers;
//std::vector<DWORD> states;
//fingers = app.getFingerList();
//states = app.getFingerState();
//std::vector<TouchData>::iterator iter;
//std::vector<DWORD>::iterator iterState;
//printf("Fingers (%d,%d): ",fingers.size(),states.size());
//for(iter=fingers.begin(),iterState=states.begin();iter!=fingers.end();iter++,iterState++)
//{
//	printf("%d_(%f,%f)_0x%x, ",iter->ID,iter->X,iter->Y,*iterState);
//}
//printf("\n");
		SLEEP(16);

	} while( ok );
	cvDestroyWindow( "mywindow" );
	//screen->saveConfig("config.xml");
	TouchScreenDevice::destroy();
	return 0;
}